#include "common.h"
#include "counter.h"
#include "Vlsu_test.h"

#define TRACE 1
#define MAX_WAVE 1000000
#define MAX_CYCLE 30000
#define ROB_SIZE 128
Vlsu_test *top;
VerilatedFstC *tfp;
Counter cycle("cycle");

bool percent(int change)
{
    uint32_t r = rand();
    r %= 100;
    return r < change;
}
uint32_t rand_n(uint32_t n)
{
    uint32_t r = rand();
    return r % n;
}
void dump()
{
#if TRACE
    if (Verilated::time() < MAX_WAVE)
    {
        tfp->dump(Verilated::time());
    }
#endif
}
void run()
{
    top->eval();
    dump();
    Verilated::timeInc(1);

    top->clk = 0;
    top->eval();
    dump();
    Verilated::timeInc(2);

    top->clk = 1;
    top->eval();
    dump();
    Verilated::timeInc(1);
}
void clear_all()
{
    top->i_valid = 0;
    top->i_dmem_valid = 0;
    top->i_rob_order_valid = 0;

    top->o_ready = 0;
    top->o_dmem_ready = 0;
}
void reset()
{
    clear_all();
    top->rst = 1;
    run();
    run();
    run();
    top->rst = 0;
}

enum
{
    MEM_READ,
    MEM_WRITE,
    _MEM_CMD_NUM
};
Counter load_cnt("load_cnt"), store_cnt("store_cnt");
void make_rand_input()
{

    static int prf_idx = 0, rob_idx = 0;
    top->i_valid = 1;
    top->i_req_cmd = rand_n(_MEM_CMD_NUM);
    top->i_req_addr = 0x80000000ull + rand_n(64) * 8;
    top->i_req_prf_idx = prf_idx++;
    top->i_req_rob_idx = rob_idx++;
    prf_idx %= ROB_SIZE;
    rob_idx %= ROB_SIZE;
    if (top->i_req_cmd == MEM_READ)
    {
        LOG(COLOR("Make Load %d", GREEN), rob_idx);
        load_cnt++;
    }
    else if (top->i_req_cmd == MEM_WRITE)
    {
        LOG(COLOR("Make Store %d", GREEN), rob_idx);
        store_cnt++;
    }
}
void clear_all_fired()
{
    if (top->i_ready & top->i_valid)
    {
        top->i_valid = 0;
    }
    top->i_rob_order_valid = 0;
    if (top->i_dmem_valid & top->i_dmem_ready)
    {
        top->i_dmem_valid = 0;
    }
}
#include <queue>
#include <list>
typedef struct
{
    uint64_t cycle;
    int prf_idx;
    int rob_idx;
    word_t addr;
    bool ordered;
} StoreEntry;

typedef struct
{
    uint64_t cycle;
    int prf_idx;
    int rob_idx;
    word_t addr;
} MEM_RSP;

void lsu_test()
{
    LOG("lsu_test");
    std::list<StoreEntry> pending_store;
    std::queue<MEM_RSP> pending_rsp;
    MEM_RSP pending_req;
    bool pending_req_valid;
    reset();
    do
    {
        if (!pending_rsp.empty() && pending_rsp.front().cycle + 2 < cycle)
        {
            top->i_dmem_valid = 1;
            top->i_dmem_rsp_rob_idx = pending_rsp.front().rob_idx;
            top->i_dmem_rsp_prf_idx = pending_rsp.front().prf_idx;
            pending_rsp.pop();
        }
        if (top->o_dmem_kill_prev)
        {
            pending_req_valid = false;
        }
        if (pending_req_valid)
        {
            for (auto store : pending_store)
            {
                Assert((pending_req.addr & (~0b111ull)) != (store.addr & (~0b111ull)), "Store Load Violation happend");
            }
            pending_rsp.push(pending_req);
        }
        if (!pending_store.empty())
        {
            auto &store = pending_store.front();
            if (store.cycle + 8 < cycle)
            {
                if (!store.ordered)
                {
                    LOG(COLOR("Wakeup Store %d", YELLOW), store.rob_idx);
                    top->i_rob_order_valid = 1;
                    top->i_rob_order_rob_idx = store.rob_idx;
                    store.ordered = true;
                }
            }
        }
        if (top->i_valid & top->i_ready)
        {
            if (top->i_req_cmd == MEM_WRITE)
            {
                LOG("Issue Store %d", top->i_req_rob_idx);
                pending_store.push_back({cycle, top->i_req_prf_idx, top->i_req_rob_idx, top->i_req_addr, false});
            }
        }
        if (top->o_dmem_valid & top->o_dmem_ready)
        {
            if (top->o_dmem_req_cmd == MEM_READ)
            {
                LOG("Send Load %d", top->o_dmem_req_rob_idx);
                pending_req = {cycle, top->o_dmem_req_prf_idx, top->o_dmem_req_rob_idx, top->o_dmem_req_addr};
            }
            else if (top->o_dmem_req_cmd == MEM_WRITE)
            {
                LOG("Send Store %d", top->o_dmem_req_rob_idx);
                Assert(!pending_store.empty(), "Pending Store should not be empty");
                auto req = pending_store.front();
                Assert(top->o_dmem_req_rob_idx == req.rob_idx, "Pending Store rob_idx %d != dmem req %d", top->o_dmem_req_rob_idx, req.rob_idx);
                pending_store.pop_front();
            }
        }
        run();
        cycle++;

        clear_all_fired();
        if ((!top->i_valid) && percent(70))
        {
            make_rand_input();
        }

        top->o_ready = percent(80);
        top->o_dmem_ready = percent(80);
        top->eval();
    } while ((!Verilated::gotFinish()) && cycle < MAX_CYCLE);
}
int main()
{
    srand(0);
    Verilated::randSeed(0);
    Verilated::randReset(1);
    top = new Vlsu_test();
#if TRACE
    Verilated::traceEverOn(true);
    tfp = new VerilatedFstC();
    top->trace(tfp, 0);
    tfp->open("Vlsu_test.fst");
#endif
    reset();
    // Run all tests
    lsu_test();
    // Final model cleanup
    run();
    top->final();
#if TRACE
    tfp->close();
    delete tfp;
#endif
    delete top;
    Counter::print_counters();
    LOG("All test done");
    return 0;
}